package org.directfire.player;

import java.util.ArrayList;
import org.directfire.player.model.Model;
import org.directfire.player.utils.NotificationUtils;
import android.app.Notification;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.WifiLock;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
import android.widget.Toast;
import com.spoledge.aacdecoder.MultiPlayer;
import com.spoledge.aacdecoder.PlayerCallback;

/**
 *   Copyright (C) 2012 Alexey Kostrjitskiy mailto:alex@ipss.net
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 * $Id: DirectFireService.java 27 2012-08-27 12:01:25Z alexeyvk@gmail.com $<br/>
 * $HeadURL: http://directfire.googlecode.com/svn/branches/0.1/DirectFireAndroid/src/org/directfire/player/DirectFireService.java $<br/><br/>
 *
 * DirectFirePlayer service.
 */
public class DirectFireService extends Service implements /*MediaPlayer.OnPreparedListener, 
	MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnInfoListener, 
	MediaPlayer.OnBufferingUpdateListener,*/ AudioManager.OnAudioFocusChangeListener{
  /** Keeps track of all current registered clients. */
  public ArrayList<Messenger> mClients = new ArrayList<Messenger>();
  
  /** reference to our player */
  private MultiPlayer spMP=null;
  /** timer thread to stop myself after some inactivity with no connection */
	private SuspendTimer sleepTimer=null;

	private static AudioManager audioManager = (AudioManager) DirectFire.getContext().getSystemService(Context.AUDIO_SERVICE);

  /** special wifi lock when playing from wifi */
  private WifiLock wifiLock = null;
	/** Model instance */
	private static Model mdl = Model.getI();
  /** initial URL */
  private static String url = "";
  /** temporary static buffer size */
  private static final int buffAudioLengthMs = 2100;//default 1500
  /** temporary static buffer size */
  private static final int buffDecodeLengthMs = 1000;//default 700
  /** ms wait before auto-play when connection restored */
  private static final int waitMsAfterConnectionRestore = 5000;
  /** wait seconds without connection before stopping service */
  private static final int withoutNetworcSecondsMax = 60*5;
  /** default volumeseek fadeout time */
  public static final int defaultVolumeBarFadeSeconds = 3;

  
  /** Notification ID*/
  private static final int NOTIFICATION_ID = 1;
  
  /**
   * Command to the service to register a client, receiving callback
   * from the service.  The Message's replyTo field must be a Messenger of
   * the client where callback should be sent.
   */
  public static final int DFS_REGISTER_CLIENT = 1;

  /**
   * Command to the service to unregister a client, to stop receiving callback
   * from the service.  The Message's replyTo field must be a Messenger of
   * the client as previously given with MSG_REGISTER_CLIENT.
   */
  public static final int DFS_UNREGISTER_CLIENT = 2;
  
  /** Command to stop service himself */
  public static final int DFS_STOP_SERVICE = 3;


  /**
   * Command to the service to play mediaplayer
   */
  public static final int DFS_PLAY = 10;

  /**
   * Command to the service to stop mediaplayer
   */
  public static final int DFS_STOP = 11;
  

  /** event occurred when connection is lost */
  public static final int MSG_EVENT_SERVICE_STOPPED = 98;
  /** event occurred when connection is lost */
  public static final int MSG_EVENT_EXCEPTION = 99;
  /** event occurred when connection is lost */
  public static final int MSG_EVENT_CONN_LOST = 100;
  /** event occurred when connection is restored */
  public static final int MSG_EVENT_CONN_RESTORED = 101;
  /** event occurred when connection state changed */
  public static final int MSG_EVENT_CONN_CHANGED = 102;
  /** event occurred when MetaData title changed */
  public static final int MSG_EVENT_TITLE_CNAGED = 200;
  /** event occurred when headphone unplugged */
  public static final int MSG_AUDIO_BECOMING_NOISY=201;
  /** event occurred when audio playback started */
  public static final int MSG_AUDIO_STARTED=210;
  /** event occurred when audio playback stopped */
  public static final int MSG_AUDIO_STOPPED=211;

  /** ectivity internal event for fading volume bar */
  public static final int MSG_FADE_OUT=250;

  /** Target we publish for clients to send messages to IncomingHandler.  */
  private final Messenger mMessenger = new Messenger(new IncomingHandler(this));
  /** Handler of incoming messages from clients.  */
  static class IncomingHandler extends Handler {
  	/** reference to our service */
  	private DirectFireService svc;
  	
  	public IncomingHandler(Service service){
  		this.svc = (DirectFireService)service;
  	}
  	
  	@Override
  	public void handleMessage(Message msg) {
  		try{
	  		switch (msg.what) {
	  		 case DFS_REGISTER_CLIENT:
	  			if(mdl.isDebug())
	  				Toast.makeText(svc, "Service client registered", Toast.LENGTH_SHORT).show();
	  			svc.mClients.add(msg.replyTo);
	  			break;
	  		 case DFS_UNREGISTER_CLIENT:
	  			if(mdl.isDebug())
	  				Toast.makeText(svc, "Service client unregistered", Toast.LENGTH_SHORT).show();
	  			svc.mClients.remove(msg.replyTo);
	  			//if(svc.mClients.size()==0){ //Not stop service when activity died
	  			//	svc.stopSelf();
	  			//}
	  			break;
	  		 case DFS_STOP_SERVICE:
	  			 if(svc.spMP!=null)
	  				 svc.spMP.stop();
	  			 svc.stopSelf();
	  			 svc.doEvent(MSG_EVENT_SERVICE_STOPPED, null);
	  			break;
	  		 case DFS_PLAY:
	  			 int result = audioManager.requestAudioFocus(svc, AudioManager.STREAM_MUSIC, 
	  			     AudioManager.AUDIOFOCUS_GAIN);
	  			 if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) 
	  				 Toast.makeText(svc, svc.getResources().getString(R.string.trs_error_request_audiofocus_stream), Toast.LENGTH_LONG).show();
	  			 else{
	  				 if(!mdl.isNoConnectivity()){
	  					 svc.initMediaPlayer();
	  					 switch(msg.getData().getInt("station")){ //TODO: accurate code
	  					 case 0:
	  						 url="http://live.wotradio.ru:8000/radio.aac";
	  						 break;
	  					 case 1:
	  						 url="http://live.wotradio.ru:8000/music.aac";
	  						 break;
	  					 case 2:
	  						 url="http://live.wotradio.ru:8000/autorj.aac";
	  						 break;
	  					 case 3:
	  						 url="http://live.wotradio.ru:8000/radio";
	  						 break;
	  					 case 4:
	  						 url="http://live.wotradio.ru:8000/music";
	  						 break;
	  					 case 5:
	  						 url="http://live.wotradio.ru:8000/autorj";
	  						 break;	  						 
	  					 }
	  					 svc.spMP.playAsync(url);
	  				 }else
	  					 Toast.makeText(svc, svc.getResources().getString(R.string.trs_no_connectivity), Toast.LENGTH_LONG).show();
	  			 }
	  			 break;
	  		 case DFS_STOP:
	  			 if(svc.spMP!=null){
	  				 svc.spMP.stop();
	  			 }
	  			 break;
	  		 default:
	  			 super.handleMessage(msg);
	  		}
	  	}catch(Exception e){
  			Bundle b = new Bundle(); b.putParcelable("exception", mdl.exception(e, 0));
      	svc.doEvent(MSG_EVENT_EXCEPTION, b);
	  	}
  	}
  }
  
  /** When service is auto created */
  @Override
  public void onCreate() {
    mdl.setmService(mMessenger);
		if(mdl.isDebug())
			Toast.makeText(this, "DirectFireService started", Toast.LENGTH_SHORT).show();
		registerReceiver(mConnReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
  	registerReceiver(mNoisyReceiver, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
  }

  /** Then service is gone, when last activity unbind it */
  @Override
  public void onDestroy() {
    if (spMP != null) { spMP.stop(); }
  	if(mdl.isDebug())
			Toast.makeText(this, "DirectFireService stopped", Toast.LENGTH_SHORT).show();
    unregisterReceiver(mConnReceiver);
		unregisterReceiver(mNoisyReceiver);
  }
  
  /** When binding to the service, we return an interface to our messenger
   *  for sending messages to the service. */
  @Override
  public IBinder onBind(Intent intent) {
      return mMessenger.getBinder();
  }
	/** send event message to all subscriber */
	private void doEvent(int event, Bundle b){
		for (int i=mClients.size()-1; i>=0; i--) {
			try {
				Message retMsg = Message.obtain(null, event);
				if(b!=null)
					retMsg.setData(b);
				mClients.get(i).send(retMsg);
			} catch (RemoteException e) {
				mClients.remove(i);
			}
		}
	}
	
  /** send command to myself*/
	private void doDFScommand(int command, Bundle b){
		Message msg=Message.obtain(null, command);
		if(b!=null)
			msg.setData(b);
		msg.replyTo = mMessenger;
		try {
				mMessenger.send(msg);
		} catch (RemoteException e) {
			throw mdl.exception(e, 402);
		}
	}

  //
  //================================ That for the MediaPlayer  ===================
  //
  /** Mediaplayer initialization */
  public void initMediaPlayer() {
  	if(spMP!=null){
  		spMP.stop();
  		while(mdl.isPlaying())
  			;
  		}
  	spMP = new MultiPlayer(clb);
  	spMP.setDecodeBufferCapacityMs(buffDecodeLengthMs);
  	spMP.setAudioBufferCapacityMs(buffAudioLengthMs);
  }
  
  PlayerCallback clb = new PlayerCallback() {
    public void playerStarted() { 
  		ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
  		if(cm.getActiveNetworkInfo()!=null && cm.getActiveNetworkInfo().getType()==ConnectivityManager.TYPE_WIFI && cm.getActiveNetworkInfo().isConnected()==true){
    		wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
    			.createWifiLock(WifiManager.WIFI_MODE_FULL, "directfireplayerlock");
    		wifiLock.acquire();
    		Log.w("WIFI_LOCK", "acquired");
    	}
    	mdl.setPlaying(true);
    	doEvent(MSG_AUDIO_STARTED, null);
    }
    public void playerPCMFeedBuffer(boolean isPlaying, int bufSizeMs, int bufCapacityMs) {
  		//Log.i("playerPCMFeedBuffer", "isPlaying: "+isPlaying+" bufSizeMs:" + bufSizeMs + " bufCapMs: " + bufCapacityMs);    	
    }
    public void playerStopped( int perf ) { 
  		if(wifiLock!=null && wifiLock.isHeld())
  			wifiLock.release();
  		mdl.setPlaying(false);
  		notificationRemove();
  		//spMP=null;//nullified for connectione vents - they are does't pulled when no playing
    }
    public void playerException( Throwable t) {
  		if(wifiLock!=null && wifiLock.isHeld())
  			wifiLock.release();
  		mdl.setPlaying(false);
  		notificationRemove();  		
    	if(t.getClass() == IllegalStateException.class )
    		;//do nothing - that when 3G/WIFI switched
    	else{
    		Bundle b = new Bundle(); b.putParcelable("exception", mdl.exception(t, 401));
    		doEvent(MSG_EVENT_EXCEPTION, b);
    	}
    }
    public void playerMetadata( String key, String value ) {
  		if(key!=null && key.equalsIgnoreCase("StreamTitle")){
      	mdl.setTrackName(value);
  			notificationUpdate(value);
      	Log.i("METADATA", "TitleChanged: "+value);
  		}
  		//else
  		//	Log.i("METADATA", key==null?"":key+": "+value==null?"":value);
    }
  };
	
	//
  //================================ That for the AudioFocus  ===================
  //
	public void onAudioFocusChange(int focusChange) {
		try{
   	 	int max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);			
   	 	switch (focusChange) {
	     case AudioManager.AUDIOFOCUS_GAIN:
	    	 // resume playback
	       if(spMP == null) 
	      	 initMediaPlayer();
				 spMP.playAsync(url);
	       audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mdl.getDuckVolume(), AudioManager.FLAG_PLAY_SOUND);
	       break;
	     case AudioManager.AUDIOFOCUS_LOSS:
	    	 // Lost focus for an unbounded amount of time: stop playback and release media player
	       if (spMP!=null);
	       	spMP.stop();
	       break;
	     case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
	    	 // Lost focus for a short time, but we have to stop
	    	 // playback. We don't release the media player because playback
	       // is likely to resume
	       if (spMP!=null)
	      	 spMP.stop();
	       break;
	     case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
	    	 // Lost focus for a short time, but it's ok to keep playing
	       // at an attenuated level
	    	 mdl.setDuckVolume(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
	    	 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, max-(max/5), AudioManager.FLAG_SHOW_UI & AudioManager.FLAG_PLAY_SOUND);
	       break;
	    }
  	}catch(Exception e){
			Bundle b = new Bundle(); b.putParcelable("exception", mdl.exception(e, 0));
    	doEvent(MSG_EVENT_EXCEPTION, b);
  	}
	}
	
  //
  //================================ That for the Notification  ===================
  //
  /** update/show notification */
  public void notificationUpdate(String sName){
		mdl.setPlayingWhenConnectionLoose(true);//for right reconnection
  	String def = getResources().getString(R.string.app_name)+"(r"+BuildInfo.revisionNumber+")";
   	Notification notification = NotificationUtils.getInstance().create(def, sName);
   	startForeground(NOTIFICATION_ID, notification);
   	doEvent(MSG_EVENT_TITLE_CNAGED, null);
		int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, 
				AudioManager.AUDIOFOCUS_GAIN);
		if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) 
			 Toast.makeText(this, getResources().getString(R.string.trs_error_request_audiofocus_stream), Toast.LENGTH_LONG).show();
  }
  
  /** remove notification */
  public void notificationRemove(){
  	stopForeground(true);
  	doEvent(MSG_AUDIO_STOPPED, null);
  	doEvent(MSG_EVENT_TITLE_CNAGED, null);
  	int result = audioManager.abandonAudioFocus(this);
		if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) 
			 Toast.makeText(this, getResources().getString(R.string.trs_error_abandon_audiofocus_stream), Toast.LENGTH_LONG).show();
  }

	//
  //================================ That for the AUDIO_BECOMING_NOISY intent  ===================
  //
	/** receiver for AUDIO_BECOMING_NOISY intent*/
	private BroadcastReceiver mNoisyReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
    	if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
     	 	int max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
        audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, max-(max/5), AudioManager.FLAG_PLAY_SOUND);
     	 	mdl.setDuckVolume(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
     	 	doEvent(MSG_AUDIO_BECOMING_NOISY, null);
    	}
    }
	};

  //
  //================================ That for the Suspend timer when no connectivity ===================
  //
	
  private class SuspendTimer extends Thread implements Runnable {
  	private int counter=0;
  	
  	public SuspendTimer(){
  		setName("SuspendTimer");
  	}
  	
  	public void run() {
      try{    		
      	while(mdl.isNoConnectivity()){
      		counter++;
      		sleep(1000);
      		if(counter==withoutNetworcSecondsMax){
      			doDFScommand(DFS_STOP_SERVICE, null);
      			return;
      		}
      	}
      } catch (InterruptedException e) {
				e.printStackTrace();
			}
      sleepTimer=null;
  	}
  }

  //
  //================================ That for the ConnectivityManager ===================
  //
  /** Receive here CONNECTIVITY_ACTION */
	final private BroadcastReceiver mConnReceiver = new BroadcastReceiver() {
     synchronized public void onReceive(Context context, Intent intent) {
			try{
				boolean noConnectivity = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
	      String reason = intent.getStringExtra(ConnectivityManager.EXTRA_REASON);
	      boolean isFailover = intent.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, false);
	      NetworkInfo currentNetworkInfo = (NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
	      Bundle b = new Bundle(); b.putParcelable("current", currentNetworkInfo);
	      b.putString("reason", reason);
	      if(noConnectivity==true){
	      	mdl.setNoConnectivity(true);
	      	if(sleepTimer==null){
	      		sleepTimer = new SuspendTimer();
	      		sleepTimer.start();
	      	}
	      	if(mdl.isPlaying()){
	      		mdl.setPlayingWhenConnectionLoose(true);
	  	      Log.d("CONNECTION_LOSED", "While playing " + currentNetworkInfo.getTypeName() 
	  	          + " reason=" + reason + " isFailover=" + isFailover + " noConnectivity=" + noConnectivity);
      			spMP.stop();
      			while(mdl.isPlaying())
      				;
	      		doEvent(MSG_EVENT_CONN_LOST, b);
	      	}
	      }else if(spMP!=null && currentNetworkInfo.isConnected()){
      		mdl.setNoConnectivity(false);
      		if(mdl.isPlaying() && isFailover){
      			mdl.setPlayingWhenConnectionLoose(true);
  	      	spMP.stop();
  	      	while(mdl.isPlaying())
  	      		;
      		}else{
	      		wait(waitMsAfterConnectionRestore);
	      		if(!mdl.isPlaying() && mdl.isPlayingWhenConnectionLoose() ){
	      			mdl.setPlayingWhenConnectionLoose(false);
	      			//TODO: use options to decision of restore playing at WIFI and 3G
	      			Log.d("CONNECTION_RESTORED", currentNetworkInfo.getTypeName() 
	    	          + " reason=" + reason + " isFailover=" + isFailover + " noConnectivity=" + noConnectivity);
	      			initMediaPlayer();
  	      		spMP.playAsync(url);
  	      		doEvent(MSG_EVENT_CONN_RESTORED, b);
	      		}
      		}
	      }
	      else if(currentNetworkInfo.isConnected())
      		mdl.setNoConnectivity(false);
	      doEvent(MSG_EVENT_CONN_CHANGED, b);
	  	}catch(Exception e){
				Bundle b = new Bundle(); b.putParcelable("exception", mdl.exception(e, 0));
	    	doEvent(MSG_EVENT_EXCEPTION, b);
	  	}
    }
	};
}
